package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author atguigu-mqx
 */
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public List<SeckillGoods> findAll() {
        //  获取缓存中所有的秒杀商品.   hvals key ;
        List<SeckillGoods> seckillGoodsList = this.redisTemplate.opsForHash().values(RedisConst.SECKILL_GOODS);

        return seckillGoodsList;
    }

    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        //  hget key field
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS, skuId.toString());

        return seckillGoods;
    }

    @Override
    public void seckillOrder(Long skuId, String userId) {
        //  校验状态位。
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)) {
            return;
        }
        //  判断用户是否下过订单. 使用命令setnx
        String seckillUserKey = RedisConst.SECKILL_USER + userId;
        //  分布式锁  set key value ex/px timeout nx/xx;
        Boolean result = this.redisTemplate.opsForValue().setIfAbsent(seckillUserKey, userId, RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        //  判断: 说明用户已经下过订单了.
        if (!result) {
            return;
        }
        //  校验库存.
        String stockSkuId = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //  判断
        if (StringUtils.isEmpty(stockSkuId)) {
            //  说明没有库存了,同时告诉其他兄弟节点，这个商品没有库存了。别卖了.
            this.redisTemplate.convertAndSend("seckillpush", skuId + ":0");
            return;
        }

        //  要存储数据:
        //  hset key = seckill:orders  field = userId  value = orderRecode
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userId);
        orderRecode.setNum(1);
        orderRecode.setSeckillGoods(this.getSeckillGoods(skuId));
        //  订单码
        orderRecode.setOrderStr(MD5.encrypt(userId + skuId));
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS, userId, orderRecode);

        //  更新 mysql ，redis.
        this.updateStockCount(skuId);

    }

    @Override
    public Result checkOrder(Long skuId, String userId) {
       /*
        1.  判断用户是否在缓存中存在   只有存在的情况下才有可能抢购成功.  至少说明你在排队.
        2.  判断用户是否抢单成功      是否有预下单记录    有：提示抢购成功，去下单.
        3.  判断用户是否下过订单      是指，提交订单按钮的时候， 在缓存中有记录，同时在数据库中也有订单记录。
        4.  校验状态位
         */
        String seckillUserKey = RedisConst.SECKILL_USER + userId;
        Boolean result = this.redisTemplate.hasKey(seckillUserKey);
        //  result = true;  说明用户已经排过队了。
        if (result){
            //  获取预下单记录.
            String seckillOrderKey = RedisConst.SECKILL_ORDERS;
            //  判断是否有预下单记录
            Boolean flag = this.redisTemplate.opsForHash().hasKey(seckillOrderKey, userId);
            if (flag){
                //  flag = true; 说明有预下单记录，提示抢购成功，去下单。
                OrderRecode orderRecode = (OrderRecode) this.redisTemplate.opsForHash().get(seckillOrderKey, userId);
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }

        //  提交订单的时候，缓存中的数据是如何存在的?  hset key = seckill:orders:users  field = userId  value = orderId
        //  判断用户是否真正下过订单，如果下过订单了，提示抢购成功，我的订单
        Boolean exist = this.redisTemplate.opsForHash().hasKey(RedisConst.SECKILL_ORDERS_USERS, userId);
        if (exist){
            //  exist = true 下过订单了，提示抢购成功，我的订单
            String orderId = (String) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS_USERS, userId);
            return Result.build(orderId,ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }
        //  校验状态位
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)) {
            return Result.build(null,ResultCodeEnum.SECKILL_ILLEGAL);
        }

        //  默认返回.
        return Result.build(null,ResultCodeEnum.SECKILL_RUN);
    }

    /**
     * 更新库存数量.
     *
     * @param skuId
     */
    @Transactional
    public void updateStockCount(Long skuId) {
        //  如果是集群部署，则需要使用分布式锁，如果是单节点部署则需要使用本地锁。
        //  Lock lock = new ReentrantLock();
        //  定义锁的key
        String lockKey = skuId + ":lock";
        RLock lock = this.redissonClient.getLock(lockKey);
        //  上锁
        lock.lock();
        try {
            //  更新谁? 先更新数据库，再缓存。
            //  获取缓存剩余库存数据
            Long stockCount = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);
            //  缓存数据库的压力.
            if (stockCount % 2 == 0) {
                //  更新缓存.  hset key field value  hget key field
                //  获取原有数据：与数据库的内容一致。
                SeckillGoods seckillGoodsQuery = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS, skuId.toString());
                seckillGoodsQuery.setStockCount(stockCount.intValue());

                //  使用id 来更新数据库.
                this.seckillGoodsMapper.updateById(seckillGoodsQuery);
                //  更新缓存。
                this.redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS, skuId.toString(), seckillGoodsQuery);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //  解锁
            lock.unlock();
        }
    }
}
